查看原文
其他

如何用 TensorFlow 让一切看起来更美?

2017-12-08 如是 GitChat技术杂谈


本文来自作者 如是 在 GitChat 上分享 「如何用 TensorFlow 让一切看起来更美?」,「阅读原文」查看交流实录。

「文末高能」

编辑 | 哈比

看到最近很多在讲 TensorFlow 的文章,忽然想写一些关于机器学习和算法基础类的文章,当一个东西每个人都在谈论的时候就少了很多机会与新鲜感。

再有意思的东西看多了也会厌烦,回头再看看自己定的这些题目有种翻看十年前的朋友圈一样的尴尬。

每天都是人工智能,打开新闻是人工智能,开会是人工智能,连用个手机都有个 AI 芯片。在这个信息爆炸的时代,有一种对于新技术的冷静的态度而又不显得与外界格格不入,这种分寸是很难把握的。

但凡事都有始有终,作为专栏的最后一篇文章就聊聊循环神经网络 (RNN),并谈谈可视化的内容。其实一直感觉作为一个靠数据过活的人,以恰当而体面的方式进行数据的可视化也是必备的技能之一。

那么上面的一段话中我是什么态度呢?正面?负面?还是二者兼而有之?“Bag of words” 模型会认为这是偏于负面的因为出现了的 “不想、厌烦、尴尬、很难” 之类的词汇,但是后面的内容显然又不止于此。

有句话说的挺好 “都 20 多年了,我们依然在用词袋子模型做文本分类”。其实 LDA 模型已经考虑了词语与词语的关系,这种关系更类似于向量的主成分,而非前后文。因此作为一个开始。

本次文章就从 RNN 模型搭建一个最简单的实例开始,那么什么是所谓的简单呢?生成文本:

RNN 入门与生成文本

RNN 模型相比于前面所说的 “Bag of words” 模型来说,可以兼顾前后文。但是以这种方式处理文本会遇到一个比较大的问题就是效率。其相比于前面所说的前馈神经网络(CNN,全链接等)更加难以训练。

优势是可以完成更加复杂的目的,比如自然语言处理。那么作为 RNN 的入门实例,本节中就从基础的开始:语句生成。TensorFlow 文档中就是给的类似的例子,可以参考 TensorFlow 文档。官方文档写的还是比较清楚的。

数据分析

其实所有机器学习算法都不难理解, 最复杂的反而是数据预处理 。这属于体力活,每天对于各种数据建模是所有人都不想面对却又不得不面对的。

相比于前面的卷积和全链接网络,RNN 网络的数据预处理可能理解起来更复杂一些,很多人都是卡在了这里而学不下去了。

从文本向量化开始就是 “词袋子 (Bag of words)” 模型,在这个模型之对于文本的向量化方式是类似于稀疏矩阵的形式。矩阵的行是每篇文章统计,列是所有文章中出现的所有的词。

很显然,这个矩阵是稀疏的,因为绝大多数情况下文章所包含的词的数量是固定的,不太可能出现一篇文章包含所有词的情况出现。

如上面所说数据矩阵 M 是稀疏的,其中所有的 m 篇文章中出现的所有词 (字) 个数为 n。这就是我们所说的词袋子模型,就像把词语随机的扔进一个袋子里。这显然只是统计词频没有任何前后文。

这种稀疏性还带来另一个问题就是两句表述意思类似的话,但是由于没有相同词语出现使得两个文章向量距离很远,就像:

  • 你技术很好,很有经验,我们需要研究一下,等通知吧

  • 不好意思面试没通过


以上两句话没有出现任何相同词语,从词袋子模型看来是完全不同的意思,但是以我们经验来看表达的意思却是相似的。于是衍生出的一种改进方式就是挑出一些词加权组合到一起,整体做一个判别,这就是 LDA 算法。

我个人非常讨厌贝叶斯理论,于是从另一个角度理解其实他就是对于矩阵 M 进行的分解。通过这种方式将一些看似不相关的内容总结到了一起,表现为 “特征向量”。

注意文中用到的一个概念就是 以我们的经验来看 这种所谓的经验也就是知识。人在判断一句话是什么意思的时候融入了个人的经验知识。而神经网络是可以保存这种经验知识的,表现为权值。

所以一般人在说 RNN 可以借助于前后文信息进行判断的时候其实他忽略了一个问题,就是 RNN 在判断的时候融入了以 往学习到的经验 

这相比于 LDA 算法又更近一步。本质来说 LDA 是对传统的只统计词频的方法的改进,而 RNN 却对于文字输入顺序也有了感知。

于是有了这一节中的主要内容,就是用神经网络完成一个语句预测的任务。首先来分析一下任务,我们需要给定一句话的前几个词,然后推测后续会出现哪些字。

这个过程是一个马尔可夫链,但是不太喜欢概率,所以直接描述过程,假设我给定一个三个字的开头:

  • 晚来天(?)(?)

根据前三个字来预测接下来两个字是什么,那么下一个字可能是晴,可能是欲,也有可能是未,那么根据概率随机选择了(欲),接下来就根据 “晚来天欲” 这四个字进行预测,可能随机选择的字是(雨),那么最终五个字就是

  • 晚来天(欲)(雨)

当然概率有随机性,可能第一步时选择的是晴,那么最终形成的诗句就是:

  • 晚来天(晴)(处)

还是因为概率,可能我们最终选择的完全不像是诗句,比如:

  • 晚来天(气)(息)

这就完全没有意境了。这里面起关键作用的实际上是给定下一步出现的字的概率,概率太平均的话也就是一种初始状态,最终可能形成完全不是人说的东西,这就很尴尬了。

所以前面我说,我并不喜欢随机性太强的东西,降低这种随机性需要持续大量的数据进行训练。概率这个很容易映射到神经网络之中就是 softmax,这个过程可以给定一个字接下来可能出现的概率。

至此其实可以看到其与前面所说的 “词袋子” 模型的文本向量化过程的区别。

在中文文本处理中与英文文本处理的一个区别就是需要进行分词处理,这是因为单个中文字与单个字母一样无法表达一个有效的信息。这就需要用几个字来表达一个意思。

对于英文来讲天然就有 “分词” 也就是空格,对于中文来讲需要人为的去完成这个过程。但是对于本次任务,我们只是对单个字行了 “编号”。

这里我们依靠的是神经网络自身的强大表达能力。于是诗句的处理就类似于一个查字典的过程:

word_dict = {',': 0, '。': 1, '\n': 2, ' 不 ': 3, ' 人 ': 4, ' 山 ': 5, ' 风 ': 6, ' 日 ': 7, ' 云 ': 8, ' 无 ': 9, ' 何 ': 10, ' 一 ': 11, ' 春 ': 12, ' 月 ': 13, ' 水 ': 14, ' 花 ': 15, ' 来 ': 16, ' 有 ': 17, ' 中 ': 18, ' 秋 ': 19, ' 上 ': 20, ' 时 ': 21, ' 天 ': 22, ' 归 ': 23, ' 心 ': 24, ' 相 ': 25, ' 此 ': 26, ' 年 ': 27, ' 生 ': 28, ' 长 ': 29, ' 夜 ': 30, ' 自 ': 31, ' 去 ': 32, ' 知 ': 33, ' 空 ': 34, ..., ' 挪 ': 5386}

举个例子来说:

  • 寒心睹肉林,飞魄看沉湎。

  • [42, 24, 1791, 1818, 72, 0, 88, 1337, 108, 670, 4206, 1, 2]


可以看到,句号、逗号以及语句结束标志 ‘\n’ 都进行了编码。这里编号的规则很简单,统计字出现的次数按顺序编码。’\n’ 等进行编码则用于判断语句的末尾,这可以辅助进行判断语句结束。

编码结束后所得的结果是一系列整型数字。如今又出现了一个问题就是编码后的一维数据并不适合神经网络处理,因为几 k 级别数字几乎可以肯定使得神经网络训练出现问题。

因此还需要的处理就是将其扩展成向量形式的表示,也就是相当于对于 one-hot 编码进行降维,TensorFlow 已经提供了相应的工具:

embedding = tf.get_variable("embedding", [len(words), rnn_size]) inputs = tf.nn.embedding_lookup(embedding, input_data)

通常而言的处理过程中的 embedding 可以用 word2vector 的方式,也就是将意思相近的词所形成的向量距离也是相近的,这可以简化神经网络结构设计。

注意这里说的是简化网络结构设计,由于 word2vec 过程可以看成是一个神经网络层,因此借助于神经网络强大的表达能力有时候是可以省略这个过程的。

本文就是省略了这个过程这个过程将编码转换为适合神经网络处理的向量。至此文本预处理工作描述完毕。

神经网络结构与训练

RNN 神经网络结构其实并不复杂。对于最传统的 RNN 结构而言只是加入了一层向量用于保存上一时间步中的输出结果:

整个过程可以写成:

对于每一个时间步  均会有一个对应的输出 。而对于多层的 RNN 网络而言是在  之上加入相同的 RNN 层:

可以看到整个过程都没有一般图示中所出现的跨层循环。以上 RNN 网络结构可以通过 BPTT 算法进行训练:

其中 对于 W 来说略有不同,因为

所以对 W 求偏导,而 也是 W 的函数:

这是一个沿着时间反向传播的过程,如果句子比较长会使得训练过程难以进行,因此在一些算法时在时间步上进行了截断。

可以看到相比于前馈神经网络,RNN 网络的复杂度提高了很多,复杂度的提升代表着自由参数的提升,也就是说神经网络的拟合、表达能力提升。在利用 Tensorflow 实现的过程中也是比较复杂的:

#yt=f(xt)cell = tf.nn.rnn_cell.BasicLSTMCell(rnn_size, state_is_tuple=True)#ht=f(xt)#yt=f(ht)cell = tf.nn.rnn_cell.MultiRNNCell([cell] * 2, state_is_tuple=True)

如此完成了两层 RNN 的搭建。可以看到,整个过程并没有 CNN 网络那么简单,而是需要一些辅助函数完成多层 RNN 网络的搭建。之后就是一个 feed 数据的过程:

outputs = []for time_step in range(self.num_steps):    if time_step > 0: tf.get_variable_scope().reuse_variables()    (cell_output, state) = cell(inputs[:, time_step, :], state)    outputs.append(cell_output)

每步中都输入一个向量化后的文字 ,而获取的输出为 cell_output,对应的输出就是 ,这里有个小问题就是每一步中都用到了相同的变量,因此需要变量复用也就是:

tf.get_variable_scope().reuse_variables()

当然有更简单的方式:

outputs, last_state = tf.nn.dynamic_rnn(cell, inputs,...)

这种方式可以直接获取输出序列。之后的训练过程就直接用输出序列与预计输出做对比。但是预计输出是一系列的整型,因而依然可以用 Tensorflow 提供的函数:

loss = tf.contrib.legacy_seq2seq.sequence_loss_by_example(...)

来简化编程过程。整个过程都需要用到一些辅助函数以帮助简化程序的编写,这也就是前面所说的 RNN 网络用 TensorFlow 实现的复杂性。

这样在 feed 数据的过程中直接输入就可以了,这里数据还是有个问题需要描述,由于我们只需要预测的是下一个词是什么因此输入的 x 和 y 之间之差一个字符:

x = [42, 24, 1791, 1818    ] y = [    24, 1791, 1818, 72]

结果与生成

首先预设一个初始字,计算下一个字出现的概率,根据概率选取词语并将其作为下一时间步的输入,循环这个过程直到遇到语句结束符 ‘\n’。如此就生成了一句诗。

来看下迭代了 40 个回合后所得的结果,写个藏头诗:

title: 《文章难写》 line1: 文字命高人,无行亦迟芳。 line2: 章句掩疑在,循容争未微。 line3: 难慕高翁乱,初行且自吟。 line4: 写月秋堂闭,流年到海深。

看起来还是有效果的,其实这个效果并不在字间结构和韵律上,而是整个过程并未控制输出长短,完全依靠神经网络自身通过判断 ‘\n’ 结束标志来控制迭代。

这就是说我们神经网络的学习过程已经可以对于五言诗进行一个很好的长短的控制。对于未训练好的网络而言其结果可能是:

title: 人工智能 line1: 人务牧牛冠,仪资擅剧额。 line2: 工佣输富寝,羸葬竟何源。 line3: 智训击绡粟,契莓桐狼。 line4: 能诱贻纳都,箫韶颇阻擎。

语句更加不通顺,而且长短有所不同。

RNN 应用与文本分类

文本分类是自然语言处理中一直比较重要的部分。这个分类包括一些语句情感的分类以或者用于垃圾邮件的鉴别。

这种自然语言处理的任务用 LDA 降维 + 算法来完成,目前来看 效果是可以的 。但是有些人会认为这不够炫,所以产生了 RNN 的需求,它可以分析前后文。

数据预处理工作

整个的数据预处理以及向量化与前面的文本生成任务没有什么不同,但是这里为了方便处理,只选择了文章中前一部分词进行处理,这是合理的,想像一下自己在阅读文章的过程中很可能没有通读全文,只是读了第一个中心句就知道文章是何种意思了。为了方便理解依然展示一下数据:

article1:游戏 ||《XXX》角逐金翎奖最佳单机游戏 9 月是金秋的季节,9 月是收获的季节。格锐数码在这个金秋为广大的玩家精心准备了大型武侠史诗游戏——《XXX》。拥有华丽的游戏画面的《XXXXX》,将带您领略仙魔玄幻世界的无穷魅力。在这个收获的季节里,格锐数码携《XXXXX》与广大玩家见面了,在新浪,QQ 等网站上均可以下载到游戏,享受游戏的新玩法和新功能······ article2:... ...

前面用双竖线分割的就是文本标签,后面是文本,本次文本分类中更加推荐用的向量化方式为 word2vec,因为可以获得更好的分类结果,但是由于训练熟练有限,所以选择的方式依然是简单的 embedding,进行单个字级别的处理。

用于分类的输出

前面说到的用于文本生成的网络生成的是每个词的概率,然后在通过概率的方式去选择下一步需要的词语 (字),而对于文本分类的数据而言则利用最后一个时间步的输出作为判别依据:

outputs, last_state = tf.nn.dynamic_rnn(cell=rnn_cell, inputs=embedding_inputs, dtype=tf.float32) last = _outputs[:, -1, :] net = slim.fully_connected(last, hidden_num,                        activation_fn=tf.nn.relu,                        scope='full1') logits = slim.fully_connected(net, class_num,                        activation_fn=tf.nn.sigmoid,                        scope='full1')

选取最后一个输出的意思是经历了所有的输入之后获取最后一个结果,这样可以对于全文进行有效的分析。输出的结果直接用于对于文本类别的预测。

注意一个问题 ,在文本生成任务之中是输出 ,而在进行文本分类任务中同样也是输出 ,这两个虽然形式相同但是目的完全不同,一个是为了预测 “字”,一个是为了预测文本类别。

两个完全不同的任务用基本类似的输出形式,这也是所谓的神经网络表达能力强大所表现的地方。其可以通过训练去自适应的调整输出目的。

结果

迭代几次后可以发现准确率可以达到:

Epoch: 6 Iter:0, Train Loss:0.3, Train Acc:91.41%, Val Loss:0.51, Val Acc:86.88%

作为对比选择如下方式进进行分析:

  • 分词

  • LDA 降维 (60)

  • 随机森林分类


如此操作之后,分类可以达到的准确率为 99%,有点颠覆世界观,这是因为一直在强调 RNN 可以分析前后文,可是对于很多的任务而言可能并不需要前后文,所以说基于词频统计的方法依然很有市场,效率是最大的问题。同时我们过度依赖了 RNN 的强大的表达能力而忽略了数据处理过程

前面说到 RNN 网络可以用于文本处理,同时其实 CNN 也可以完成相同的任务,有个文献说多层的 CNN 结构可能比几层 RNN 结构分类效果更好。但这也不一定,但是可以肯定的是 CNN 比较好训练。

可视化部分

可视化个人以为一直是数据分析工作中非常重要的部分,好的可视化可以起到事半功倍的效果。经常听说的一句话就是神经网络的可解释性比较差。

也就是说通常来说用于解释的可视化是比较困难的,只是有一个训练好的网络结构,在此之上就是直接输出结果了。

就像一个 “黑盒”,所以说其可视化过程相比较来说是比较困难的,但是为了做例子,选择将 LDA 的内容做一些可视化。

这里再提一句,其实很多降维操作的一个主要好处就是可以进行可视化,而这种可视化做出来效果是很好的。

词云

词云表示的方式简单直观。可以直接对所谓的主题 (其实就是矩阵分解后的向量) 进行直接可视化工作:


以上是选择文本降维后所用的 “主题”,很明显的可以看出部分主题写的是什么。所有文章大约都是对于以上的几个主题进行的加权组合。词语可以很好的表现这种权重之间的区别,表现为词的大小。

当然用 Echarts 是很容易的进行文本的可视化的,当然没有用到 python 的 json 的库,而是直接:

data = []for itr, idx in enumerate(topic_words): data.append({'name':idx, 'value':np.exp(-itr/100)+1})if(itr > 50):breakjson_str = str(data)

这是一个简单粗暴而且投机取巧的方式。可能直到目前都没有描述上面是什么意思。

所以这里先给一个 实例,实际上 echarts 是以 json 数据格式作为交换的,而 python 的字符串与 json 的数据格式有着天然的相似性,看下图:

直接对 python 的字典、列表进行字符串转换,就可以直接获取类似于 js 格式的数据。

比重与权值

比重与权值的可视化是比较方便的可以利用 Echarts 所提供的饼图和平行线,这里魔改官方实例:

以上就是对于主题的一些可视化,可能配色还是比较成问题,改个背景什么的就好,配色问题一直是最难的。

这里用到的就是列表的类型转换了。

其他

目前来说数据分析的可视化都是集中于一些饼图、线图进行的整合与分析,比如这种:

当然上面这个图形是 p 的,但是这个图形实现起来并不困难,只要稍微懂一些 html。但是麻烦的是美工,稍微差一些的配色就会使得整个图形观感差很多,比如我上面做的这个错误的示范。

当然最喜好的 echarts 的是它的动态图形功能,比如做的一个流体模拟的动态:

当然这些只是为可视化提供一些思路。

写在最后

至此专栏算是写完了,以 RNN 作为一个结束是比较合适的。本身专栏的一个目标就在于介绍 TensorFlow 的一些使用。

全链接网络和卷积神经网络均是简单可以实现的,在理解设计结构之后可以很容易的进行分析处理和训练,而 RNN 则相对复杂一些,需要用到很多的辅助函数,还有一些内容诸如 DropOut 没讲,这是训练过程优化的部分。

RNN 网络作为一个复杂的实现,其强大表现为时序输出,这同时需要我们做一个思维的转换,比如可以将 CNN 的结果当成时序数据进行 RNN 处理,这方面的应用比如语音识别,还有见下图:

识别一些不定长的验证码,这是 RNN+CNN 的例子,CNN 部分用于识别图形,而 RNN 将其进行串联起来。

目前的神经网络研究工作集中于两个方面:第一个是网络模型的设计,比如 ResNet 等;还有一个是训练过程的优化。这两方面实际上都需要很强的数学基础。

在工作之中用到需要我们去借鉴一些现有文献中的设计好的结构。之于说从头完成一个神经网络设计,以目前的情况来看实际上是非常复杂的,这属于真正意义上的创新。而工作只是应用而已,并无创造的必要。

神经网络还有很多的应用场景,这些根据场景选择一些网络结构是合适的,通常也有很多应用方面的文章可供参考。长期只是看 CNN 处理图形、RNN 处理文本其实会极大的限制自己的想象力。

这对于算法设计是不利的。同时还需要说的一点就是上世纪一些简单的机器学习算法虽然简单,但是其中的可解释性、计算效率等方面依然不是深度神经网络所能比拟的。

深度神经网络并非无所不能,不要过于神话。

近期热文

Web 安全:前端攻击 XSS 深入解析

300万粉丝,全国最大的线上抽奖平台,深度解析

高可用、高性能? 接口设计的 16 个原则

【钓鱼】与【反钓鱼】的技术剖析

快速了解 Java 9 平台模块系统



「阅读原文」看交流实录,你想知道的都在这里

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存